#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"

#include "klib/printf.hpp"
#include <cassert>

// TEST_CASE("printf") {
//     klib::printf("name = %s\n", "kiven");
// }

TEST_CASE("printf") {

    char buf[1024];
    memset(buf, 'x', sizeof(buf));
    buf[sizeof(buf) - 1] = 0;

    REQUIRE(sizeof(buf) >= 1024);

    SUBCASE("int 8/32/64") {
        uint8_t u8 = (uint8_t) -1;
        uint32_t i32 = (uint32_t) -1;
        uint64_t i64 = (uint64_t) -1;
        std::string s = "i8 = -1, 255, i32 = -1, 4294967295, i64 = -1, 18446744073709551615";
        size_t size = klib::snprintf(buf, sizeof(buf), "i8 = %d, %u, i32 = %d, %u, i64 = %lld, %llu", (int8_t) u8, u8, (int32_t) i32, i32, (int64_t) i64, i64);
        CHECK(size == s.size());
        CHECK(s == buf);
    }

    SUBCASE("float/double") {
        float f = 24.035;
        double d = 56.77531;
        const char* fmt = "f = %f(%g), d = %lf(%lg), .2f = %.2f";
        size_t size = klib::snprintf(buf, sizeof(buf), fmt, f, f, d, d, 23.0);
        char buf2[1024];
        size_t size2 = std::sprintf(buf2, fmt, f, f, d, d, 23.0);
        CHECK(size == size2);
        CHECK(std::string(buf) == buf2);
    }

    SUBCASE("str/bool") {
        const char* str = "Hello World";
        std::string_view name = "kiven";
        std::string s = "str = Hello World, name = kiven, bool = false, true";
        size_t size = klib::snprintf(buf, sizeof(buf), "str = %s, name = %S, bool = %B, %B", str, &name, false, true);
        CHECK(size == s.size());
        CHECK(std::string(buf) == s);
    }

    SUBCASE("formatter with width") {
        std::string s = "4d = [  23], 04d = [0023], .5d = [00023]";
        size_t size = klib::snprintf(buf, sizeof(buf), "4d = [%4d], 04d = [%04d], .5d = [%.5d]", 23, 23, 23);
        CHECK(size == s.size());
        CHECK(s == buf);
    }

    SUBCASE("datetime") {
        time_t t = 1657390330; // 2022-07-10 02:12:10
        tm tm_ = *localtime(&t);
        std::string s = "datetime = [2022-07-10 02:12:10], tm = [2022-07-10 02:12:10], date = [2022-07-10], time = [02:12:10]";
        size_t size = klib::snprintf(buf, sizeof(buf), "datetime = [%T], tm = [%lT], date = [%-T], time = [%+T]", t, &tm_, t, t);
        CHECK(size == s.size());
        CHECK(s == buf);
    }

    SUBCASE("nullptr") {
        std::string s = "nullptr = [], 04.2f = [1.20] 04.2g = [01.2]";
        size_t size = klib::snprintf(buf, sizeof(buf), "nullptr = [%s], 04.2f = [%04.2f] 04.2g = [%04.2g]", nullptr, 1.2, 1.2);
        CHECK(size == s.size());
        CHECK(s == buf);
    }

    SUBCASE("with fixed width") {
        std::string s = "12";
        size_t size = klib::snprintf(buf, sizeof(buf), "%.2s", "123");
        CHECK(size == s.size());
        CHECK(s == buf);
    }

    SUBCASE("return size") {
        std::string s = "12";
        size_t size = klib::snprintf(buf, 3, "%s", "12345");
        CHECK_MESSAGE(size == 5, "size = ", size);
        CHECK(s == buf);
        size = klib::sprintf_len("%s", "123456");
        CHECK(size == 6);
        size = klib::snprintf(nullptr, 0, "%s", "123456");
        CHECK(size == 6);
        char b[7] = {1, 1, 1, 1, 1, 1, 1};
        size = klib::snprintf(b, 6, "%s", "123456");
        std::string s2(b);
        CHECK(s2.size() + 1 == size);
        CHECK(s2 == "12345");
        size = klib::snprintf(b, 7, "%s", "123456");
        std::string s3(b);
        CHECK(s3.size() == size);
        CHECK(s3 == "123456");
    }
}

TEST_CASE("printf_custom") {
    SUBCASE("fnprintf") {
        std::string s("49, kiven");
        struct A { char buf[256]; size_t pos; } arg{{}, 0};
        size_t size = klib::fnprintf(&arg, +[](void* arg, char c) { ((A*)arg)->buf[((A*)arg)->pos++] = c; }, "%i, %s", 49, "kiven");
        CHECK_MESSAGE(size == s.size(), "size = ", size);
        CHECK(s == arg.buf);
    }
    
    SUBCASE("bufprintf") {
        std::string s("49, kiven");
        char buf[2];
        struct A { char data[256]; size_t pos; } a{{}, 0};
        size_t size = klib::bufprintf(buf, sizeof(buf), &a, +[](void* a, const char* data, size_t len) {
            std::memcpy(((A*)a)->data + ((A*)a)->pos, data, len);
            ((A*)a)->pos += len;
        }, "%i, %s", 49, "kiven");
        CHECK_MESSAGE(size == s.size(), "size = ", size);
        CHECK_MESSAGE(a.pos == s.size(), "size = ", size);
        CHECK(s == a.data);
    }

    SUBCASE("aprintf") {
        std::string s("49, kiven");
        std::string buf;
        std::string_view ret = klib::aprintf([&buf](size_t size) { buf.resize(size); return buf.data(); }, "%i, %s", 49, "kiven");
        CHECK(ret.size() == s.size());
        CHECK(buf.size() == s.size());
        CHECK(s == buf);
    }
    
    SUBCASE("sprintf") {
        std::string s("123, 49, kiven");
        std::string buf("123, ");
        size_t size = klib::sprintf(buf, "%i, %s", 49, "kiven");
        CHECK(size == 9);
        CHECK(buf.size() == s.size());
        CHECK(s == buf);
    }

    SUBCASE("printf_string") {
        std::string s("49, kiven");
        std::string buf = klib::printf_string("%i, %s", 49, "kiven");
        CHECK(buf.size() == s.size());
        CHECK(s == buf);
    }
}